New York City Taxi Trip Duration

평가함수(Evalution)

  • RMSLE(Root Mean Squared Logarithmic Error) \[\epsilon = \sqrt{\frac{1}{n} \sum_{i=1}^n (\log(p_i + 1) - \log(a_i+1))^2 }\]
id,trip_duration
id00001,978
id00002,978
id00003,978
id00004,978
etc.

Data 소개

  • 경쟁 데이터 세트는 Google Cloud Platform의 Big Query에서 제공되는 2016 년 NYC Yellow Cab 여행 기록 데이터를 기반으로합니다.

  • 이 데이터는 원래 NYC 택시 및 리무진위원회 (TLC)에서 발간 한 것입니다.

  • 데이터는 이 놀이터 경쟁의 목적을 위해 샘플링되고 청소되었습니다.
  • 참가자는 개별 여행 속성에 따라 테스트 세트의 각 여행 기간을 예측해야 합니다.

데이터 필드

  • id : 각 출장의 고유 식별자
  • vendor_id : 여행 기록과 연결된 공급자를 나타내는 코드
  • pickup_datetime : 미터가 작동 된 날짜와 시간
  • dropoff_datetime : 미터가 분리 된 날짜와 시간
  • passenger_count : 차량의 승객 수 (운전자가 입력 한 값)
  • pickup_longitude : 미터가 사용 된 경도
  • pickup_latitude : 미터가 사용 된 위도
  • dropoff_longitude : 미터가 분리 된 경도
  • dropoff_latitude : 미터가 분리 된 위도
  • store_and_fwd_flag : 플래그는 자동차가 서버에 연결되어 있지 않아 여행 기록이 차량 메모리에 보관되었는지 여부를 나타냅니다.
  • Y = 저장 및 전달; N = 상점 및 순회 여행 불가

  • trip_duration : 여행 기간 (초)

  • 면책 조항 : 커널에서 사용할 확장 된 변수 집합을 제공하기 위해 데이터 집합 순서에서 드롭 오프 좌표를 제거하지 않기로 결정했습니다.

NYC Taxi Interactive EDA

  • 이상열 (캐글뽀개기)
#install.packages(c('flexdashboard', 'TraMineR', 'leaflet', 'treemap', 'highcharter', 'zoo')
library(data.table)
library(dplyr)
library(ggplot2)
library(flexdashboard)
library(TraMineR)
library(highcharter)
library(DT)
library(flexdashboard)
library(leaflet)
library(rmarkdown)
library(treemap)
library(viridisLite)
library(tidyverse)
  • 2016 년 NYC의 날씨와 함께 데이터 세트를 사용하기로 결정 했으므로 이 데이터 세트를 합치려면 몇 가지 데이터가 필요합니다.

METAR

  • METAR는 날씨 정보를 보고 하는 형식입니다.
  • METAR 기상 예보는 비행 전 날씨 브리핑의 일부를 수행하는 조종사와 기상 예측에 도움이되는 집계 된 METAR 정보를 사용하는 기상 학자에 의해 주로 사용됩니다.

  • 이것은 KNYC에서 2016에 대한 METARs 집계 정보입니다.

train_dataset <- fread("./data/train.csv")

Read 0.0% of 1458644 rows
Read 17.8% of 1458644 rows
Read 34.3% of 1458644 rows
Read 52.8% of 1458644 rows
Read 72.0% of 1458644 rows
Read 89.8% of 1458644 rows
Read 1458644 rows and 11 (of 11) columns from 0.187 GB file in 00:00:08
train_dataset[, pi_dt_shift :=  paste(substr(pickup_datetime, 1, 13), ":00:00", sep = "")]
train_dataset[, df_dt_shift := paste(substr(dropoff_datetime, 1, 13), ":00:00", sep = "")]
weather_nyc <-  fread("./data/KNYC_Metars.csv")
head(train_dataset, 5)
head(weather_nyc, 5)
weather_condition_freq <- 
  weather_nyc %>%                        group_by(Conditions) %>%
  select(Conditions) %>%
  summarize(count = n()) %>%
  arrange(desc(count))
datatable(weather_condition_freq)
  • 다음 단계는 2 개의 데이터 세트를 결합하고 몇 가지 요약 통계를 보여주는 것입니다.
train_joined <- dplyr::left_join(train_dataset, weather_nyc, by  = c("pi_dt_shift" = "Time"))
train_joined$Conditions[is.na(train_joined$Conditions) ==  TRUE] <- "Unknown"
weather_condition_freq <- train_joined %>%
  group_by(Conditions) %>%
  select(Conditions,trip_duration ) %>%
  summarize(count = n(), 
            mean_dur = mean(trip_duration, na.rm = TRUE), 
            sd_dur =   sd(trip_duration, na.rm = TRUE), 
            median_dur = median(trip_duration, na.rm = TRUE))
datatable(weather_condition_freq)
  • 아래의 그림은 사용자가 픽업 택시를 다른 기상 조건에 얼마나 자주 의존하는지 보여줍니다. ^^

  • NA가 있는 조건 값을 ’알수 없는 카테고리’로 변경하기로 결정했습니다.

  • 가장 빈번한 그룹은 ‘Clear’ 조건을 가진 그룹이라는 것이 분명합니다.

highchart()%>%
hc_add_series(weather_condition_freq, "column", hcaes(x =  Conditions, y = count), name = "Count by Conditions Weather") %>%
  hc_plotOptions(series = list(
    showInLegend = FALSE, 
    pointFormat = "{point.y}%"
  ), 
  column = list(colorByPoint = TRUE)) %>%
  hc_subtitle(text = "Count by Conditions Caegories") %>%
  hc_credits(
    enabled = TRUE, 
    text = "Source: Kaggle", 
    href = "https://kaggle.com/damianpanek", 
    style = list(fontSize = "12px")
  ) %>%
  hc_add_theme(hc_theme_google())
highchart()%>%
  hc_add_series(weather_condition_freq, "spline", hcaes(x =  Conditions, y = mean_dur), name = "Mean Trip Duration") %>%
  hc_add_series(weather_condition_freq, "spline", hcaes(x =  Conditions, y = median_dur), name = "Median Trip Duration") %>%
  hc_add_series(weather_condition_freq, "spline", hcaes(x =  Conditions, y = sd_dur), name = "SD Trip Duration") %>%
  hc_plotOptions(series = list(
    showInLegend = TRUE, 
    pointFormat = "{point.y}%"
  ), 
  column = list(colorByPoint = TRUE)) %>%
  hc_subtitle(text = "Count by Conditions Caegories") %>%
  hc_credits(
    enabled = TRUE, 
    text = "Source: Kaggle", 
    href = "https://kaggle.com/damianpanek", 
    style = list(fontSize = "12px")
  ) %>%
  hc_add_theme(hc_theme_google())
  • 작은 데이터 변환. 일 / 월 및 관찰 요일에 대한 정보를 얻고 싶습니다.
train_joined <- data.table(train_joined)
train_joined <- train_joined[is.na(pickup_datetime) == FALSE,  ]
train_joined[, pickup_datetime := as.POSIXct(pickup_datetime, format = "%Y-%m-%d %H:%M:%S")]
train_joined[, dropoff_datetime := as.POSIXct(dropoff_datetime, format = "%Y-%m-%d %H:%M:%S")]
train_joined[, pickup_day := format(pickup_datetime, "%Y-%m-%d")]
train_joined[, pickup_month := format(pickup_datetime, "%Y-%m")]
train_joined[, dropoff_day := format(dropoff_datetime, "%Y-%m-%d")]
train_joined[, dropoff_month := format(dropoff_datetime, "%Y-%m")]
train_joined[, weekday := weekdays(pickup_datetime)]
  • Summary Statistics for Tempertarure in NYC taxi dataset
weather_temp_day <-  train_joined %>% 
  group_by(pickup_day) %>%
  select(pickup_day, Temp., Conditions) %>%
  summarize(count = n(), 
            min = min(Temp., na.rm = TRUE), 
            max = max(Temp., na.rm = TRUE), 
            sd_dur = sd(Temp., na.rm = TRUE))
datatable(weather_temp_day)
hchart(weather_temp_day, 
        type = "columnrange", 
        hcaes(x = pickup_day, low = min, high = max, color = sd_dur)) %>%
        hc_chart(polar = TRUE) %>%
    hc_yAxis(max = 30,  min = -10, labels = list(format = "{value} "), 
             showFirstLabel = FALSE) %>%
  hc_xAxis(
  title = list(text = ""), gridLineWidth = 0.5,
  labels = list(format = "{value: %b}")) %>%
  hc_add_theme(hc_theme_google()) %>%
hc_title(text = "Min/Max temperature daily, coloured by SD(Temp)")
  • Similar plot - Summary statistics for Trip Duration variable
weather_dur_day <-  train_joined %>% 
  group_by(pickup_day) %>%
  select(pickup_day, trip_duration, Conditions) %>%
  summarize(count = n(), 
            median = median(trip_duration, na.rm = TRUE), 
            mean = mean(trip_duration, na.rm = TRUE), 
            sd_dur = sd(trip_duration, na.rm = TRUE))
datatable(weather_dur_day)
hchart(weather_dur_day, 
       type = "columnrange", 
       hcaes(x = pickup_day, low = mean, high = median, color = median)) %>%
  hc_chart(polar = TRUE) %>%
  hc_yAxis( max = 1300, labels = list(format = "{value} "), 
           showFirstLabel = FALSE) %>%
  hc_xAxis(
    title = list(text = ""), gridLineWidth = 0.5,
    labels = list(format = "{value: %b}")) %>%
  hc_add_theme(hc_theme_google()) %>% 
  hc_title(text = "Trip duration Statistics per day")
  • Piechart for fwd/store flag
store_and_fwd_freq <- train_dataset %>% 
  select(store_and_fwd_flag) %>%
  group_by(store_and_fwd_flag) %>%
  summarize(count = n()) %>%
  mutate(freq = count/sum(count))
datatable(store_and_fwd_freq)
hc <-  highchart() %>%
      hc_add_series(store_and_fwd_freq, "pie", hcaes(x =  store_and_fwd_flag, y = count), name = "Column Plot") %>%
  hc_plotOptions(series = list(
    showInLegend = FALSE, 
    pointFormat = "{point.y}%"
  ), 
  column = list(colorByPoint = TRUE)) %>%
  hc_subtitle(text = "Frequency of Store And FWD FLAG") %>%
  hc_credits(
    enabled = TRUE, 
    text = "Source: Kaggle", 
    href = "https://kaggle.com/damianpanek", 
    style = list(fontSize = "12px")
  ) %>%
  hc_add_theme(hc_theme_google())
  • Frequency plot - day by day
freq_by_day <- train_joined %>%
              select(pickup_day) %>%
              group_by(pickup_day) %>%
              summarize(count = n())
datatable(freq_by_day)
freq_day <- highchart() %>%
            hc_add_series(freq_by_day, "column", 
                          hcaes(x = pickup_day, y = count),name = "Column")  %>%
                          hc_add_theme(hc_theme_google()) %>%
                          hc_plotOptions(
                            series = list(
                              showInLegend = FALSE, 
                              pointFormat = "{point.y}%"
                            ), 
                            column = list(
                              colorByPoint = TRUE
                            )
                          ) %>% 
  hc_yAxis(title = list("pickup per Day"), 
           labels = list(format = "{value}"))   %>%
  hc_xAxis(unique(as.character(freq_by_day$pickup_day))) %>%
  hc_title(
    text = "Graph represents amount of pickups per day"
  ) %>%
  hc_subtitle(text = "In sweet rainbow dash taste XD") %>%
  hc_credits(
    enabled = TRUE, text = "Damiano ;p/click",
    href = "https://www.kaggle.com/damianpanek"
  ) %>%
  hc_add_theme(hc_theme_google())
freq_day
  • Similar plot but observation divided by month
freq_by_month <- train_joined %>%
  select(pickup_month) %>%
  group_by(pickup_month) %>%
  summarize(count = n())
datatable(freq_by_month)
freq_month <- highchart() %>%
  hc_add_series(freq_by_month, "column", 
                hcaes(x = pickup_month, y = count),name = "Column")  %>%
  hc_add_theme(hc_theme_google()) %>%
  hc_plotOptions(
    series = list(
      showInLegend = FALSE, 
      pointFormat = "{point.y}%"
    ), 
    column = list(
      colorByPoint = TRUE
    )
  ) %>% 
  hc_yAxis(title = list("pickup per Month"), 
           labels = list(format = "{value}"))   %>%
  hc_xAxis( unique(as.character(freq_by_month$pickup_month))) %>%
  hc_title(
    text = "Graph represents amount of pickups per day"
  ) %>%
  hc_subtitle(text = "UP 20170723") %>%
  hc_credits(
    enabled = TRUE, text = "Damiano ;p/click",
    href = "https://www.kaggle.com/damianpanek"
  )
freq_month
freq_by_day_trip <- train_joined %>%
  select(pickup_day, trip_duration) %>%
  
  group_by(pickup_day) %>%
  summarize(count = n(), 
            mean_trip = mean(trip_duration, na.rm = TRUE), 
            median_trip = median(trip_duration, na.rm = TRUE), 
            sd_trip     = sd(trip_duration, na.rm = TRUE))
datatable(freq_by_day_trip)
hc_by_day <- highchart() %>%
  hc_plotOptions(
    series = list(
      showInLegend = FALSE, 
      pointFormat = "{point.y}%"
    ), 
    column = list(
      colorByPoint = TRUE
    )
  ) %>% 
  highchart() %>%
  hc_add_series(freq_by_day_trip, "line",  hcaes(x = pickup_day, y = mean_trip),name = "Mean") %>%
  hc_add_series(freq_by_day_trip,   "line" , hcaes(x=  pickup_day,  y= median_trip), name = "median") %>%
  hc_add_series(freq_by_day_trip, "line", hcaes(x =  pickup_day, y = sd_trip), name = "sd") %>% 
  hc_add_theme(hc_theme_google()) %>%
  hc_title(text = "Summary statistics by Day of pickup :)") %>%
  hc_plotOptions(
    series = list(
      showInLegend = FALSE, 
      pointFormat = "{point.y}%"
    ), 
    column = list(
      colorByPoint = TRUE
    )
  ) %>% 
  hc_yAxis(title = list("Values/day"), 
           labels = list(format = "{value}"))   %>%
  hc_subtitle(text = "Summary statistics grouped by day") %>%
  hc_credits(
    enabled = TRUE, text = "Damiano ;p/click",
    href = "https://www.kaggle.com/damianpanek"
  )
hc_by_day
freq_by_month_trip <- train_joined %>%
    select(pickup_month, trip_duration) %>%
    group_by(pickup_month)  %>%
    summarize(count  = n(), 
              mean_trip = mean(trip_duration, na.rm = TRUE), 
              median_trip = median(trip_duration, na.rm = TRUE), 
              sd_trip = sd(trip_duration, na.rm = TRUE))
datatable(freq_by_month_trip)
hc_by_month <- highchart() %>%
  hc_plotOptions(
    series = list(
      showInLegend = FALSE, 
      pointFormat = "{point.y}%"
    ), 
    column = list(
      colorByPoint = TRUE
    )
  ) %>% 
  highchart() %>%
  hc_add_series(freq_by_month_trip, "line",  hcaes(x = pickup_month, y = mean_trip),name = "Mean") %>%
  hc_add_series(freq_by_month_trip,   "line" , hcaes(x=  pickup_month,  y= median_trip), name = "median") %>%
  hc_add_series(freq_by_month_trip, "line", hcaes(x =  pickup_month, y = sd_trip), name = "sd") %>% 
  hc_xAxis(categories = c("2016-01", "2016-02", "2016-03", "2016-04", "2016-05", "2016-06")) %>%
  hc_add_theme(hc_theme_google()) %>%
  hc_title(text = "Summary statistics by Month of pickup :)")
  
hc_by_month                

Leaflet section

  • 먼저 순서를 만들려면 행을 끌어서 선택해야 합니다. 다음 makecluster 옵션을 사용하여 전단을 작성하기로 결정했습니다.
#install.packages('leaflet.extras')
library(leaflet)
library(leaflet.extras)
lon_lat <- train_joined[, c("pickup_longitude", "pickup_latitude", 
"dropoff_longitude", "dropoff_latitude")]
lon_lat$rown <- as.numeric(rownames(lon_lat))
lon_min <- lon_lat[rown < 300 ,]
str(lon_min)
Classes ‘data.table’ and 'data.frame':  299 obs. of  5 variables:
 $ pickup_longitude : num  -74 -74 -74 -74 -74 ...
 $ pickup_latitude  : num  40.8 40.7 40.8 40.7 40.8 ...
 $ dropoff_longitude: num  -74 -74 -74 -74 -74 ...
 $ dropoff_latitude : num  40.8 40.7 40.7 40.7 40.8 ...
 $ rown             : num  1 2 3 4 5 6 7 8 9 10 ...
 - attr(*, ".internal.selfref")=<externalptr> 
drop <- lon_min[, c("pickup_longitude", "pickup_latitude", "rown")]
pick <- lon_min[, c("dropoff_longitude", "dropoff_latitude", "rown")]
colnames(drop)  <- c("lon", "lat", "rown")
colnames(pick) <- colnames(drop)
all_bin_min <- bind_rows(drop, pick)
all_bin_min$rown2 <- rep(1:nrow(all_bin_min)+1/2,each = 2)
Supplied 1196 items to be assigned to 598 items of column 'rown2' (598 unused)
leaflet(data = all_bin_min) %>% addTiles() %>%
  addCircles(~lon, ~lat) %>%
  addPolygons(data = all_bin_min, lng = ~lon, 
               lat = ~lat, 
               stroke = 0.03, color =  "blue", weight = 0.4, 
               opacity = 1.2)  %>% enableMeasurePath() 
  • Leaflex plot with makecluster options
 leaflet(data = train_joined[1:50000, ]) %>% addTiles() %>%
  addMarkers(~pickup_longitude, ~pickup_latitude, clusterOptions = markerClusterOptions()) 
  • Leaflet heatmap
train_count <- train_joined %>% 
                select(pickup_latitude, pickup_longitude) %>%
                group_by(pickup_latitude, pickup_longitude) %>%
                summarize(count = n())
train_count <- train_count[train_count$count >1,]
 leaflet(data = train_count) %>% addTiles() %>% 
 addHeatmap(lng = ~pickup_longitude, lat = ~pickup_latitude, intensity = ~count,
             blur = 20, max = 0.05, radius = 15)
  • Pickup grouped by month
train_count <- train_joined %>% 
                select(pickup_latitude, pickup_longitude, pickup_month) %>%
                group_by(pickup_latitude, pickup_longitude, pickup_month) %>%
                summarize(count = n())
train_count <- train_count[train_count$count >1,]
 leaflet(data = train_count) %>% addTiles() %>% 
 addHeatmap(lng = ~pickup_longitude, lat = ~pickup_latitude,
 layerId = ~pickup_month, group = ~pickup_month, intensity = ~count,
             blur = 20, max = 0.05, radius = 15)
  • Frequency by day of week :)
count_weekday <- train_joined %>%
                  select(weekday) %>%
                  group_by(weekday) %>%
                  summarize(count = n())
count_weekday <- data.table(count_weekday)
count_weekday <- count_weekday[is.na(weekday)  ==  FALSE, ]
count_weekday <- data.frame(count_weekday)
tm <- treemap(count_weekday , index = c("weekday"),
              vSize = "count")

hctreemap(tm)
LS0tCnRpdGxlOiAiVGF4aShSKSDrhbjtirjrtoEiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OiBkZWZhdWx0CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAotLS0KIyMgTmV3IFlvcmsgQ2l0eSBUYXhpIFRyaXAgRHVyYXRpb24KCi0gS2FnZ2xl7J2AIOuJtOyaleyLnOyXkOyEnCDtg53si5wg7Jes7ZaJ7J2YIOy0nSDso7ztlokg6rGw66as66W8IOyYiOy4oe2VmOuKlCDrqqjrjbjsnYQg66eM65Oc64qUIOqyg+yXkCDrj4TsoITtlZjqs6Ag7J6I7Iq164uI64ukLiAKCi0g6riw67O4IOuNsOydtO2EsCDshLjtirjripQg7ZS97JeFIOyLnOqwhCwg7KeA66as7KCBIOyijO2RnCwg7Iq56rCdIOyImCDrsI8g6riw7YOAIOyXrOufrCDrs4DsiJjqsIAg7Y+s7ZWoIOuQnCBOWUMg7YOd7IucIOuwjyDrpqzrrLTsp4TsnITsm5Dtmozsl5DshJwg67Cc6riJIO2VnCDrjbDsnbTthLAg7IS47Yq47J6F64uI64ukLgoKLSDsmrDrpqzqsIAgMjAxNSDrhYTsl5Ag7KO87LWcIO2VnCBFQ01MIC8gUEtERCDsl6ztlokg7Iuc6rCEIOuPhOyghOqzvCDsnKDsgqztlZjri6TripQg6rKD7J2EIOyduOygle2VoCDqsoPsnoXri4jri6QuIOq3uOufrOuCmCDsnbQg64+E7KCE7J2AIOuSpOyjveuwleyjveyeheuLiOuLpC4gCgotIOyasOumrOuKlCDri6Trpbgg7LC46rCA7J6Q6rCAIOyekOyLoOydmCDsmIjsuKHsl5Ag7IKs7Jqp7ZWgIOyImOyeiOuKlCDstpTqsIAg6rWQ7JyhIOuNsOydtO2EsOulvCDqsozsi5ztlZjrj4TroZ0gKO2YhOq4iCDsg4HquIjqs7wg7ZWo6ruYKSDri7nsi6DsnYQg6rKp66Ck7ZWp64uI64ukLiAKCi0g7Jqw66as64qUIOy7pOuupOuLiO2LsOyXkCDtirntnogg7Ya17LCw66ClIOyeiOqxsOuCmCDqsIDsuZjsnojripQg7Luk64SQIOyekeyEseyekOyXkOqyjCDrs7Tsg4HtlZjquLAg7JyE7ZW0IOqyqeyjvCDrsI8g7LWc7KKFIOyDgeydhCDsp4DsoJXtlojsirXri4jri6QuCgoKIyMjIO2PieqwgO2VqOyImChFdmFsdXRpb24pCi0gUk1TTEUoUm9vdCBNZWFuIFNxdWFyZWQgTG9nYXJpdGhtaWMgRXJyb3IpCiQkXGVwc2lsb24gPSBcc3FydHtcZnJhY3sxfXtufSBcc3VtX3tpPTF9Xm4gKFxsb2cocF9pICsgMSkgLSBcbG9nKGFfaSsxKSleMiB9JCQKCmBgYAppZCx0cmlwX2R1cmF0aW9uCmlkMDAwMDEsOTc4CmlkMDAwMDIsOTc4CmlkMDAwMDMsOTc4CmlkMDAwMDQsOTc4CmV0Yy4KYGBgCgojIyMgRGF0YSDshozqsJwKLSDqsr3sn4Eg642w7J207YSwIOyEuO2KuOuKlCBHb29nbGUgQ2xvdWQgUGxhdGZvcm3snZggQmlnIFF1ZXJ57JeQ7IScIOygnOqzteuQmOuKlCAyMDE2IOuFhCBOWUMgWWVsbG93IENhYiDsl6ztlokg6riw66GdIOuNsOydtO2EsOulvCDquLDrsJjsnLzroZztlanri4jri6QuIAoKLSDsnbQg642w7J207YSw64qUIOybkOuemCBOWUMg7YOd7IucIOuwjyDrpqzrrLTsp4TsnITsm5DtmowgKFRMQynsl5DshJwg67Cc6rCEIO2VnCDqsoPsnoXri4jri6QuIAoKLSDrjbDsnbTthLDripQg7J20IOuGgOydtO2EsCDqsr3sn4HsnZgg66qp7KCB7J2EIOychO2VtCDsg5jtlIzrp4HrkJjqs6Ag7LKt7IaM65CY7JeI7Iq164uI64ukLiAKLSDssLjqsIDsnpDripQg6rCc67OEIOyXrO2WiSDsho3shLHsl5Ag65Sw6528IO2FjOyKpO2KuCDshLjtirjsnZgg6rCBIOyXrO2WiSDquLDqsITsnYQg7JiI7Lih7ZW07JW8IO2VqeuLiOuLpC4KCiMjIyDrjbDsnbTthLAg7ZWE65OcCgotIGlkIDog6rCBIOy2nOyepeydmCDqs6DsnKAg7Iud67OE7J6QCi0gdmVuZG9yX2lkIDog7Jes7ZaJIOq4sOuhneqzvCDsl7DqsrDrkJwg6rO16riJ7J6Q66W8IOuCmO2DgOuCtOuKlCDsvZTrk5wKLSBwaWNrdXBfZGF0ZXRpbWUgOiDrr7jthLDqsIAg7J6R64+ZIOuQnCDrgqDsp5zsmYAg7Iuc6rCECi0gZHJvcG9mZl9kYXRldGltZSA6IOuvuO2EsOqwgCDrtoTrpqwg65CcIOuCoOynnOyZgCDsi5zqsIQKLSBwYXNzZW5nZXJfY291bnQgOiDssKjrn4nsnZgg7Iq56rCdIOyImCAo7Jq07KCE7J6Q6rCAIOyeheugpSDtlZwg6rCSKQotIHBpY2t1cF9sb25naXR1ZGUgOiDrr7jthLDqsIAg7IKs7JqpIOuQnCDqsr3rj4QKLSBwaWNrdXBfbGF0aXR1ZGUgOiDrr7jthLDqsIAg7IKs7JqpIOuQnCDsnITrj4QKLSBkcm9wb2ZmX2xvbmdpdHVkZSA6IOuvuO2EsOqwgCDrtoTrpqwg65CcIOqyveuPhAotIGRyb3BvZmZfbGF0aXR1ZGUgOiDrr7jthLDqsIAg67aE66asIOuQnCDsnITrj4QKLSBzdG9yZV9hbmRfZndkX2ZsYWcgOiDtlIzrnpjqt7jripQg7J6Q64+Z7LCo6rCAIOyEnOuyhOyXkCDsl7DqsrDrkJjslrQg7J6I7KeAIOyViuyVhCDsl6ztlokg6riw66Gd7J20IOywqOufiSDrqZTrqqjrpqzsl5Ag67O06rSA65CY7JeI64qU7KeAIOyXrOu2gOulvCDrgpjtg4Drg4Xri4jri6QuIAogIC0gWSA9IOyggOyepSDrsI8g7KCE64usOyBOID0g7IOB7KCQIOuwjyDsiJztmowg7Jes7ZaJIOu2iOqwgAoKLSB0cmlwX2R1cmF0aW9uIDog7Jes7ZaJIOq4sOqwhCAo7LSIKQoKKiDrqbTssYUg7KGw7ZWtIDog7Luk64SQ7JeQ7IScIOyCrOyaqe2VoCDtmZXsnqUg65CcIOuzgOyImCDsp5HtlansnYQg7KCc6rO17ZWY6riwIOychO2VtCDrjbDsnbTthLAg7KeR7ZWpIOyInOyEnOyXkOyEnCDrk5zroa0g7Jik7ZSEIOyijO2RnOulvCDsoJzqsbDtlZjsp4Ag7JWK6riw66GcIOqysOygle2WiOyKteuLiOuLpC4KCgojIyMgTllDIFRheGkgSW50ZXJhY3RpdmUgRURBCgotIOydtOyDgeyXtCAo7LqQ6riA672A6rCc6riwKQoKCmBgYHtyfQojaW5zdGFsbC5wYWNrYWdlcyhjKCdmbGV4ZGFzaGJvYXJkJywgJ1RyYU1pbmVSJywgJ2xlYWZsZXQnLCAndHJlZW1hcCcsICdoaWdoY2hhcnRlcicsICd6b28nKQoKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZmxleGRhc2hib2FyZCkKbGlicmFyeShUcmFNaW5lUikKbGlicmFyeShoaWdoY2hhcnRlcikKbGlicmFyeShEVCkKbGlicmFyeShmbGV4ZGFzaGJvYXJkKQpsaWJyYXJ5KGxlYWZsZXQpCmxpYnJhcnkocm1hcmtkb3duKQpsaWJyYXJ5KHRyZWVtYXApCmxpYnJhcnkodmlyaWRpc0xpdGUpCmxpYnJhcnkodGlkeXZlcnNlKQpgYGAKCi0gMjAxNiDrhYQgTllD7J2YIOuCoOyUqOyZgCDtlajqu5gg642w7J207YSwIOyEuO2KuOulvCDsgqzsmqntlZjquLDroZwg6rKw7KCVIO2WiOycvOuvgOuhnCDsnbQg642w7J207YSwIOyEuO2KuOulvCDtlansuZjroKTrqbQg66qHIOqwgOyngCDrjbDsnbTthLDqsIAg7ZWE7JqU7ZWp64uI64ukLgoKIyMjIFtNRVRBUl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvTUVUQVIpCi0gTUVUQVLripQg64Kg7JSoIOygleuztOulvCDrs7Tqs6Ag7ZWY64qUIO2YleyLneyeheuLiOuLpC4gCi0gTUVUQVIg6riw7IOBIOyYiOuztOuKlCDruYTtlokg7KCEIOuCoOyUqCDruIzrpqztlZHsnZgg7J2867aA66W8IOyImO2Wie2VmOuKlCDsobDsooXsgqzsmYAg6riw7IOBIOyYiOy4oeyXkCDrj4Tsm4DsnbTrkJjripQg7KeR6rOEIOuQnCBNRVRBUiDsoJXrs7Trpbwg7IKs7Jqp7ZWY64qUIOq4sOyDgSDtlZnsnpDsl5Ag7J2Y7ZW0IOyjvOuhnCDsgqzsmqnrkKnri4jri6QuCgotIOydtOqyg+ydgCBLTllD7JeQ7IScIDIwMTbsl5Ag64yA7ZWcIE1FVEFScyDsp5Hqs4Qg7KCV67O07J6F64uI64ukLgoKYGBge3J9CnRyYWluX2RhdGFzZXQgPC0gZnJlYWQoIi4vZGF0YS90cmFpbi5jc3YiKQoKdHJhaW5fZGF0YXNldFssIHBpX2R0X3NoaWZ0IDo9ICBwYXN0ZShzdWJzdHIocGlja3VwX2RhdGV0aW1lLCAxLCAxMyksICI6MDA6MDAiLCBzZXAgPSAiIildCnRyYWluX2RhdGFzZXRbLCBkZl9kdF9zaGlmdCA6PSBwYXN0ZShzdWJzdHIoZHJvcG9mZl9kYXRldGltZSwgMSwgMTMpLCAiOjAwOjAwIiwgc2VwID0gIiIpXQoKd2VhdGhlcl9ueWMgPC0gIGZyZWFkKCIuL2RhdGEvS05ZQ19NZXRhcnMuY3N2IikKCmhlYWQodHJhaW5fZGF0YXNldCwgNSkKaGVhZCh3ZWF0aGVyX255YywgNSkKYGBgCgpgYGB7cn0Kd2VhdGhlcl9jb25kaXRpb25fZnJlcSA8LSAKICB3ZWF0aGVyX255YyAlPiUgICAgICAgICAgICAgICAgICAgICAgICBncm91cF9ieShDb25kaXRpb25zKSAlPiUKICBzZWxlY3QoQ29uZGl0aW9ucykgJT4lCiAgc3VtbWFyaXplKGNvdW50ID0gbigpKSAlPiUKICBhcnJhbmdlKGRlc2MoY291bnQpKQoKZGF0YXRhYmxlKHdlYXRoZXJfY29uZGl0aW9uX2ZyZXEpCmBgYAoKLSDri6TsnYwg64uo6rOE64qUIDIg6rCc7J2YIOuNsOydtO2EsCDshLjtirjrpbwg6rKw7ZWp7ZWY6rOgIOuqhyDqsIDsp4Ag7JqU7JW9IO2GteqzhOulvCDrs7Tsl6zso7zripQg6rKD7J6F64uI64ukLgoKYGBge3J9CnRyYWluX2pvaW5lZCA8LSBkcGx5cjo6bGVmdF9qb2luKHRyYWluX2RhdGFzZXQsIHdlYXRoZXJfbnljLCBieSAgPSBjKCJwaV9kdF9zaGlmdCIgPSAiVGltZSIpKQoKdHJhaW5fam9pbmVkJENvbmRpdGlvbnNbaXMubmEodHJhaW5fam9pbmVkJENvbmRpdGlvbnMpID09ICBUUlVFXSA8LSAiVW5rbm93biIKCndlYXRoZXJfY29uZGl0aW9uX2ZyZXEgPC0gdHJhaW5fam9pbmVkICU+JQogIGdyb3VwX2J5KENvbmRpdGlvbnMpICU+JQogIHNlbGVjdChDb25kaXRpb25zLHRyaXBfZHVyYXRpb24gKSAlPiUKICBzdW1tYXJpemUoY291bnQgPSBuKCksIAogICAgICAgICAgICBtZWFuX2R1ciA9IG1lYW4odHJpcF9kdXJhdGlvbiwgbmEucm0gPSBUUlVFKSwgCiAgICAgICAgICAgIHNkX2R1ciA9ICAgc2QodHJpcF9kdXJhdGlvbiwgbmEucm0gPSBUUlVFKSwgCiAgICAgICAgICAgIG1lZGlhbl9kdXIgPSBtZWRpYW4odHJpcF9kdXJhdGlvbiwgbmEucm0gPSBUUlVFKSkKCgpkYXRhdGFibGUod2VhdGhlcl9jb25kaXRpb25fZnJlcSkKYGBgCgotIOyVhOuemOydmCDqt7jrprzsnYAg7IKs7Jqp7J6Q6rCAIO2UveyXhSDtg53si5zrpbwg64uk66W4IOq4sOyDgSDsobDqsbTsl5Ag7Ja866eI64KYIOyekOyjvCDsnZjsobTtlZjripTsp4Ag67O07Jes7KSN64uI64ukLiBeXgoKLSBOQeqwgCDsnojripQg7KGw6rG0IOqwkuydhCAn7JWM7IiYIOyXhuuKlCDsubTthYzqs6Drpqwn66GcIOuzgOqyve2VmOq4sOuhnCDqsrDsoJXtlojsirXri4jri6QuCgotIOqwgOyepSDruYjrsojtlZwg6re466O57J2AICdDbGVhcicg7KGw6rG07J2EIOqwgOynhCDqt7jro7nsnbTrnbzripQg6rKD7J20IOu2hOuqhe2VqeuLiOuLpC4KCmBgYHtyfQpoaWdoY2hhcnQoKSAlPiUKaGNfYWRkX3Nlcmllcyh3ZWF0aGVyX2NvbmRpdGlvbl9mcmVxLCAiY29sdW1uIiwgaGNhZXMoeCA9ICBDb25kaXRpb25zLCB5ID0gY291bnQpLCBuYW1lID0gIkNvdW50IGJ5IENvbmRpdGlvbnMgV2VhdGhlciIpICU+JQogIGhjX3Bsb3RPcHRpb25zKHNlcmllcyA9IGxpc3QoCiAgICBzaG93SW5MZWdlbmQgPSBGQUxTRSwgCiAgICBwb2ludEZvcm1hdCA9ICJ7cG9pbnQueX0lIgogICksIAogIGNvbHVtbiA9IGxpc3QoY29sb3JCeVBvaW50ID0gVFJVRSkpICU+JQogIGhjX3N1YnRpdGxlKHRleHQgPSAiQ291bnQgYnkgQ29uZGl0aW9ucyBDYWVnb3JpZXMiKSAlPiUKICBoY19jcmVkaXRzKAogICAgZW5hYmxlZCA9IFRSVUUsIAogICAgdGV4dCA9ICJTb3VyY2U6IEthZ2dsZSIsIAogICAgaHJlZiA9ICJodHRwczovL2thZ2dsZS5jb20vZGFtaWFucGFuZWsiLCAKICAgIHN0eWxlID0gbGlzdChmb250U2l6ZSA9ICIxMnB4IikKICApICU+JQogIGhjX2FkZF90aGVtZShoY190aGVtZV9nb29nbGUoKSkKYGBgCgpgYGB7cn0KaGlnaGNoYXJ0KCklPiUKICBoY19hZGRfc2VyaWVzKHdlYXRoZXJfY29uZGl0aW9uX2ZyZXEsICJzcGxpbmUiLCBoY2Flcyh4ID0gIENvbmRpdGlvbnMsIHkgPSBtZWFuX2R1ciksIG5hbWUgPSAiTWVhbiBUcmlwIER1cmF0aW9uIikgJT4lCiAgaGNfYWRkX3Nlcmllcyh3ZWF0aGVyX2NvbmRpdGlvbl9mcmVxLCAic3BsaW5lIiwgaGNhZXMoeCA9ICBDb25kaXRpb25zLCB5ID0gbWVkaWFuX2R1ciksIG5hbWUgPSAiTWVkaWFuIFRyaXAgRHVyYXRpb24iKSAlPiUKICBoY19hZGRfc2VyaWVzKHdlYXRoZXJfY29uZGl0aW9uX2ZyZXEsICJzcGxpbmUiLCBoY2Flcyh4ID0gIENvbmRpdGlvbnMsIHkgPSBzZF9kdXIpLCBuYW1lID0gIlNEIFRyaXAgRHVyYXRpb24iKSAlPiUKICBoY19wbG90T3B0aW9ucyhzZXJpZXMgPSBsaXN0KAogICAgc2hvd0luTGVnZW5kID0gVFJVRSwgCiAgICBwb2ludEZvcm1hdCA9ICJ7cG9pbnQueX0lIgogICksIAogIGNvbHVtbiA9IGxpc3QoY29sb3JCeVBvaW50ID0gVFJVRSkpICU+JQogIGhjX3N1YnRpdGxlKHRleHQgPSAiQ291bnQgYnkgQ29uZGl0aW9ucyBDYWVnb3JpZXMiKSAlPiUKICBoY19jcmVkaXRzKAogICAgZW5hYmxlZCA9IFRSVUUsIAogICAgdGV4dCA9ICJTb3VyY2U6IEthZ2dsZSIsIAogICAgaHJlZiA9ICJodHRwczovL2thZ2dsZS5jb20vZGFtaWFucGFuZWsiLCAKICAgIHN0eWxlID0gbGlzdChmb250U2l6ZSA9ICIxMnB4IikKICApICU+JQogIGhjX2FkZF90aGVtZShoY190aGVtZV9nb29nbGUoKSkKYGBgCgotIOyekeydgCDrjbDsnbTthLAg67OA7ZmYLiDsnbwgLyDsm5Qg67CPIOq0gOywsCDsmpTsnbzsl5Ag64yA7ZWcIOygleuztOulvCDslrvqs6Ag7Iu27Iq164uI64ukLgoKYGBge3J9CnRyYWluX2pvaW5lZCA8LSBkYXRhLnRhYmxlKHRyYWluX2pvaW5lZCkKdHJhaW5fam9pbmVkIDwtIHRyYWluX2pvaW5lZFtpcy5uYShwaWNrdXBfZGF0ZXRpbWUpID09IEZBTFNFLCAgXQoKdHJhaW5fam9pbmVkWywgcGlja3VwX2RhdGV0aW1lIDo9IGFzLlBPU0lYY3QocGlja3VwX2RhdGV0aW1lLCBmb3JtYXQgPSAiJVktJW0tJWQgJUg6JU06JVMiKV0KdHJhaW5fam9pbmVkWywgZHJvcG9mZl9kYXRldGltZSA6PSBhcy5QT1NJWGN0KGRyb3BvZmZfZGF0ZXRpbWUsIGZvcm1hdCA9ICIlWS0lbS0lZCAlSDolTTolUyIpXQp0cmFpbl9qb2luZWRbLCBwaWNrdXBfZGF5IDo9IGZvcm1hdChwaWNrdXBfZGF0ZXRpbWUsICIlWS0lbS0lZCIpXQp0cmFpbl9qb2luZWRbLCBwaWNrdXBfbW9udGggOj0gZm9ybWF0KHBpY2t1cF9kYXRldGltZSwgIiVZLSVtIildCgp0cmFpbl9qb2luZWRbLCBkcm9wb2ZmX2RheSA6PSBmb3JtYXQoZHJvcG9mZl9kYXRldGltZSwgIiVZLSVtLSVkIildCnRyYWluX2pvaW5lZFssIGRyb3BvZmZfbW9udGggOj0gZm9ybWF0KGRyb3BvZmZfZGF0ZXRpbWUsICIlWS0lbSIpXQoKdHJhaW5fam9pbmVkWywgd2Vla2RheSA6PSB3ZWVrZGF5cyhwaWNrdXBfZGF0ZXRpbWUpXQpgYGAKCi0gU3VtbWFyeSBTdGF0aXN0aWNzIGZvciBUZW1wZXJ0YXJ1cmUgaW4gTllDICAgdGF4aSBkYXRhc2V0CgpgYGB7cn0Kd2VhdGhlcl90ZW1wX2RheSA8LSAgdHJhaW5fam9pbmVkICU+JSAKICBncm91cF9ieShwaWNrdXBfZGF5KSAlPiUKICBzZWxlY3QocGlja3VwX2RheSwgVGVtcC4sIENvbmRpdGlvbnMpICU+JQogIHN1bW1hcml6ZShjb3VudCA9IG4oKSwgCiAgICAgICAgICAgIG1pbiA9IG1pbihUZW1wLiwgbmEucm0gPSBUUlVFKSwgCiAgICAgICAgICAgIG1heCA9IG1heChUZW1wLiwgbmEucm0gPSBUUlVFKSwgCiAgICAgICAgICAgIHNkX2R1ciA9IHNkKFRlbXAuLCBuYS5ybSA9IFRSVUUpKQoKCmRhdGF0YWJsZSh3ZWF0aGVyX3RlbXBfZGF5KQpgYGAKCmBgYHtyfQpoY2hhcnQod2VhdGhlcl90ZW1wX2RheSwgCiAgICAgICAgdHlwZSA9ICJjb2x1bW5yYW5nZSIsIAogICAgICAgIGhjYWVzKHggPSBwaWNrdXBfZGF5LCBsb3cgPSBtaW4sIGhpZ2ggPSBtYXgsIGNvbG9yID0gc2RfZHVyKSkgJT4lCiAgICAgICAgaGNfY2hhcnQocG9sYXIgPSBUUlVFKSAlPiUKICAgIGhjX3lBeGlzKG1heCA9IDMwLCAgbWluID0gLTEwLCBsYWJlbHMgPSBsaXN0KGZvcm1hdCA9ICJ7dmFsdWV9ICIpLCAKICAgICAgICAgICAgIHNob3dGaXJzdExhYmVsID0gRkFMU0UpICU+JQogIGhjX3hBeGlzKAogIHRpdGxlID0gbGlzdCh0ZXh0ID0gIiIpLCBncmlkTGluZVdpZHRoID0gMC41LAogIGxhYmVscyA9IGxpc3QoZm9ybWF0ID0gInt2YWx1ZTogJWJ9IikpICU+JQogIGhjX2FkZF90aGVtZShoY190aGVtZV9nb29nbGUoKSkgJT4lCmhjX3RpdGxlKHRleHQgPSAiTWluL01heCB0ZW1wZXJhdHVyZSBkYWlseSwgY29sb3VyZWQgYnkgU0QoVGVtcCkiKQpgYGAKCi0gU2ltaWxhciBwbG90IC0gU3VtbWFyeSBzdGF0aXN0aWNzIGZvciBUcmlwIER1cmF0aW9uIHZhcmlhYmxlCgpgYGB7cn0Kd2VhdGhlcl9kdXJfZGF5IDwtICB0cmFpbl9qb2luZWQgJT4lIAogIGdyb3VwX2J5KHBpY2t1cF9kYXkpICU+JQogIHNlbGVjdChwaWNrdXBfZGF5LCB0cmlwX2R1cmF0aW9uLCBDb25kaXRpb25zKSAlPiUKICBzdW1tYXJpemUoY291bnQgPSBuKCksIAogICAgICAgICAgICBtZWRpYW4gPSBtZWRpYW4odHJpcF9kdXJhdGlvbiwgbmEucm0gPSBUUlVFKSwgCiAgICAgICAgICAgIG1lYW4gPSBtZWFuKHRyaXBfZHVyYXRpb24sIG5hLnJtID0gVFJVRSksIAogICAgICAgICAgICBzZF9kdXIgPSBzZCh0cmlwX2R1cmF0aW9uLCBuYS5ybSA9IFRSVUUpKQoKCmRhdGF0YWJsZSh3ZWF0aGVyX2R1cl9kYXkpCmBgYAoKYGBge3J9CmhjaGFydCh3ZWF0aGVyX2R1cl9kYXksIAogICAgICAgdHlwZSA9ICJjb2x1bW5yYW5nZSIsIAogICAgICAgaGNhZXMoeCA9IHBpY2t1cF9kYXksIGxvdyA9IG1lYW4sIGhpZ2ggPSBtZWRpYW4sIGNvbG9yID0gbWVkaWFuKSkgJT4lCiAgaGNfY2hhcnQocG9sYXIgPSBUUlVFKSAlPiUKICBoY195QXhpcyggbWF4ID0gMTMwMCwgbGFiZWxzID0gbGlzdChmb3JtYXQgPSAie3ZhbHVlfSAiKSwgCiAgICAgICAgICAgc2hvd0ZpcnN0TGFiZWwgPSBGQUxTRSkgJT4lCiAgaGNfeEF4aXMoCiAgICB0aXRsZSA9IGxpc3QodGV4dCA9ICIiKSwgZ3JpZExpbmVXaWR0aCA9IDAuNSwKICAgIGxhYmVscyA9IGxpc3QoZm9ybWF0ID0gInt2YWx1ZTogJWJ9IikpICU+JQogIGhjX2FkZF90aGVtZShoY190aGVtZV9nb29nbGUoKSkgJT4lIAogIGhjX3RpdGxlKHRleHQgPSAiVHJpcCBkdXJhdGlvbiBTdGF0aXN0aWNzIHBlciBkYXkiKQpgYGAKCi0gUGllY2hhcnQgZm9yIGZ3ZC9zdG9yZSBmbGFnCgpgYGB7cn0Kc3RvcmVfYW5kX2Z3ZF9mcmVxIDwtIHRyYWluX2RhdGFzZXQgJT4lIAogIHNlbGVjdChzdG9yZV9hbmRfZndkX2ZsYWcpICU+JQogIGdyb3VwX2J5KHN0b3JlX2FuZF9md2RfZmxhZykgJT4lCiAgc3VtbWFyaXplKGNvdW50ID0gbigpKSAlPiUKICBtdXRhdGUoZnJlcSA9IGNvdW50L3N1bShjb3VudCkpCgpkYXRhdGFibGUoc3RvcmVfYW5kX2Z3ZF9mcmVxKQpgYGAKCmBgYHtyfQpoYyA8LSAgaGlnaGNoYXJ0KCkgJT4lCiAgICAgIGhjX2FkZF9zZXJpZXMoc3RvcmVfYW5kX2Z3ZF9mcmVxLCAicGllIiwgaGNhZXMoeCA9ICBzdG9yZV9hbmRfZndkX2ZsYWcsIHkgPSBjb3VudCksIG5hbWUgPSAiQ29sdW1uIFBsb3QiKSAlPiUKICBoY19wbG90T3B0aW9ucyhzZXJpZXMgPSBsaXN0KAogICAgc2hvd0luTGVnZW5kID0gRkFMU0UsIAogICAgcG9pbnRGb3JtYXQgPSAie3BvaW50Lnl9JSIKICApLCAKICBjb2x1bW4gPSBsaXN0KGNvbG9yQnlQb2ludCA9IFRSVUUpKSAlPiUKICBoY19zdWJ0aXRsZSh0ZXh0ID0gIkZyZXF1ZW5jeSBvZiBTdG9yZSBBbmQgRldEIEZMQUciKSAlPiUKICBoY19jcmVkaXRzKAogICAgZW5hYmxlZCA9IFRSVUUsIAogICAgdGV4dCA9ICJTb3VyY2U6IEthZ2dsZSIsIAogICAgaHJlZiA9ICJodHRwczovL2thZ2dsZS5jb20vZGFtaWFucGFuZWsiLCAKICAgIHN0eWxlID0gbGlzdChmb250U2l6ZSA9ICIxMnB4IikKICApICU+JQogIGhjX2FkZF90aGVtZShoY190aGVtZV9nb29nbGUoKSkKYGBgCgotIEZyZXF1ZW5jeSBwbG90IC0gZGF5IGJ5IGRheSAKCmBgYHtyfQpmcmVxX2J5X2RheSA8LSB0cmFpbl9qb2luZWQgJT4lCiAgICAgICAgICAgICAgc2VsZWN0KHBpY2t1cF9kYXkpICU+JQogICAgICAgICAgICAgIGdyb3VwX2J5KHBpY2t1cF9kYXkpICU+JQogICAgICAgICAgICAgIHN1bW1hcml6ZShjb3VudCA9IG4oKSkKCmRhdGF0YWJsZShmcmVxX2J5X2RheSkKYGBgCgpgYGB7cn0KZnJlcV9kYXkgPC0gaGlnaGNoYXJ0KCkgJT4lCiAgICAgICAgICAgIGhjX2FkZF9zZXJpZXMoZnJlcV9ieV9kYXksICJjb2x1bW4iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBoY2Flcyh4ID0gcGlja3VwX2RheSwgeSA9IGNvdW50KSxuYW1lID0gIkNvbHVtbiIpICAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICBoY19hZGRfdGhlbWUoaGNfdGhlbWVfZ29vZ2xlKCkpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgIGhjX3Bsb3RPcHRpb25zKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VyaWVzID0gbGlzdCgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2hvd0luTGVnZW5kID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwb2ludEZvcm1hdCA9ICJ7cG9pbnQueX0lIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2x1bW4gPSBsaXN0KAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvckJ5UG9pbnQgPSBUUlVFCiAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgICAgICAgICAgKSAlPiUgCiAgaGNfeUF4aXModGl0bGUgPSBsaXN0KCJwaWNrdXAgcGVyIERheSIpLCAKICAgICAgICAgICBsYWJlbHMgPSBsaXN0KGZvcm1hdCA9ICJ7dmFsdWV9IikpICAgJT4lCiAgaGNfeEF4aXModW5pcXVlKGFzLmNoYXJhY3RlcihmcmVxX2J5X2RheSRwaWNrdXBfZGF5KSkpICU+JQogIGhjX3RpdGxlKAogICAgdGV4dCA9ICJHcmFwaCByZXByZXNlbnRzIGFtb3VudCBvZiBwaWNrdXBzIHBlciBkYXkiCiAgKSAlPiUKICBoY19zdWJ0aXRsZSh0ZXh0ID0gIkluIHN3ZWV0IHJhaW5ib3cgZGFzaCB0YXN0ZSBYRCIpICU+JQogIGhjX2NyZWRpdHMoCiAgICBlbmFibGVkID0gVFJVRSwgdGV4dCA9ICJEYW1pYW5vIDtwL2NsaWNrIiwKICAgIGhyZWYgPSAiaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9kYW1pYW5wYW5layIKICApICU+JQogIGhjX2FkZF90aGVtZShoY190aGVtZV9nb29nbGUoKSkKCmZyZXFfZGF5CmBgYAoKLSBTaW1pbGFyIHBsb3QgYnV0IG9ic2VydmF0aW9uIGRpdmlkZWQgYnkgbW9udGgKCmBgYHtyfQpmcmVxX2J5X21vbnRoIDwtIHRyYWluX2pvaW5lZCAlPiUKICBzZWxlY3QocGlja3VwX21vbnRoKSAlPiUKICBncm91cF9ieShwaWNrdXBfbW9udGgpICU+JQogIHN1bW1hcml6ZShjb3VudCA9IG4oKSkKCmRhdGF0YWJsZShmcmVxX2J5X21vbnRoKQpgYGAKCmBgYHtyfQpmcmVxX21vbnRoIDwtIGhpZ2hjaGFydCgpICU+JQogIGhjX2FkZF9zZXJpZXMoZnJlcV9ieV9tb250aCwgImNvbHVtbiIsIAogICAgICAgICAgICAgICAgaGNhZXMoeCA9IHBpY2t1cF9tb250aCwgeSA9IGNvdW50KSxuYW1lID0gIkNvbHVtbiIpICAlPiUKICBoY19hZGRfdGhlbWUoaGNfdGhlbWVfZ29vZ2xlKCkpICU+JQogIGhjX3Bsb3RPcHRpb25zKAogICAgc2VyaWVzID0gbGlzdCgKICAgICAgc2hvd0luTGVnZW5kID0gRkFMU0UsIAogICAgICBwb2ludEZvcm1hdCA9ICJ7cG9pbnQueX0lIgogICAgKSwgCiAgICBjb2x1bW4gPSBsaXN0KAogICAgICBjb2xvckJ5UG9pbnQgPSBUUlVFCiAgICApCiAgKSAlPiUgCiAgaGNfeUF4aXModGl0bGUgPSBsaXN0KCJwaWNrdXAgcGVyIE1vbnRoIiksIAogICAgICAgICAgIGxhYmVscyA9IGxpc3QoZm9ybWF0ID0gInt2YWx1ZX0iKSkgICAlPiUKICBoY194QXhpcyggdW5pcXVlKGFzLmNoYXJhY3RlcihmcmVxX2J5X21vbnRoJHBpY2t1cF9tb250aCkpKSAlPiUKICBoY190aXRsZSgKICAgIHRleHQgPSAiR3JhcGggcmVwcmVzZW50cyBhbW91bnQgb2YgcGlja3VwcyBwZXIgZGF5IgogICkgJT4lCiAgaGNfc3VidGl0bGUodGV4dCA9ICJVUCAyMDE3MDcyMyIpICU+JQogIGhjX2NyZWRpdHMoCiAgICBlbmFibGVkID0gVFJVRSwgdGV4dCA9ICJEYW1pYW5vIDtwL2NsaWNrIiwKICAgIGhyZWYgPSAiaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9kYW1pYW5wYW5layIKICApCgpmcmVxX21vbnRoCmBgYAoKYGBge3J9CmZyZXFfYnlfZGF5X3RyaXAgPC0gdHJhaW5fam9pbmVkICU+JQogIHNlbGVjdChwaWNrdXBfZGF5LCB0cmlwX2R1cmF0aW9uKSAlPiUKICAKICBncm91cF9ieShwaWNrdXBfZGF5KSAlPiUKICBzdW1tYXJpemUoY291bnQgPSBuKCksIAogICAgICAgICAgICBtZWFuX3RyaXAgPSBtZWFuKHRyaXBfZHVyYXRpb24sIG5hLnJtID0gVFJVRSksIAogICAgICAgICAgICBtZWRpYW5fdHJpcCA9IG1lZGlhbih0cmlwX2R1cmF0aW9uLCBuYS5ybSA9IFRSVUUpLCAKICAgICAgICAgICAgc2RfdHJpcCAgICAgPSBzZCh0cmlwX2R1cmF0aW9uLCBuYS5ybSA9IFRSVUUpKQoKCmRhdGF0YWJsZShmcmVxX2J5X2RheV90cmlwKQpgYGAKCmBgYHtyfQpoY19ieV9kYXkgPC0gaGlnaGNoYXJ0KCkgJT4lCiAgaGNfcGxvdE9wdGlvbnMoCiAgICBzZXJpZXMgPSBsaXN0KAogICAgICBzaG93SW5MZWdlbmQgPSBGQUxTRSwgCiAgICAgIHBvaW50Rm9ybWF0ID0gIntwb2ludC55fSUiCiAgICApLCAKICAgIGNvbHVtbiA9IGxpc3QoCiAgICAgIGNvbG9yQnlQb2ludCA9IFRSVUUKICAgICkKICApICU+JSAKICBoaWdoY2hhcnQoKSAlPiUKICBoY19hZGRfc2VyaWVzKGZyZXFfYnlfZGF5X3RyaXAsICJsaW5lIiwgIGhjYWVzKHggPSBwaWNrdXBfZGF5LCB5ID0gbWVhbl90cmlwKSxuYW1lID0gIk1lYW4iKSAlPiUKICBoY19hZGRfc2VyaWVzKGZyZXFfYnlfZGF5X3RyaXAsICAgImxpbmUiICwgaGNhZXMoeD0gIHBpY2t1cF9kYXksICB5PSBtZWRpYW5fdHJpcCksIG5hbWUgPSAibWVkaWFuIikgJT4lCiAgaGNfYWRkX3NlcmllcyhmcmVxX2J5X2RheV90cmlwLCAibGluZSIsIGhjYWVzKHggPSAgcGlja3VwX2RheSwgeSA9IHNkX3RyaXApLCBuYW1lID0gInNkIikgJT4lIAogIGhjX2FkZF90aGVtZShoY190aGVtZV9nb29nbGUoKSkgJT4lCiAgaGNfdGl0bGUodGV4dCA9ICJTdW1tYXJ5IHN0YXRpc3RpY3MgYnkgRGF5IG9mIHBpY2t1cCA6KSIpICU+JQogIGhjX3Bsb3RPcHRpb25zKAogICAgc2VyaWVzID0gbGlzdCgKICAgICAgc2hvd0luTGVnZW5kID0gRkFMU0UsIAogICAgICBwb2ludEZvcm1hdCA9ICJ7cG9pbnQueX0lIgogICAgKSwgCiAgICBjb2x1bW4gPSBsaXN0KAogICAgICBjb2xvckJ5UG9pbnQgPSBUUlVFCiAgICApCiAgKSAlPiUgCiAgaGNfeUF4aXModGl0bGUgPSBsaXN0KCJWYWx1ZXMvZGF5IiksIAogICAgICAgICAgIGxhYmVscyA9IGxpc3QoZm9ybWF0ID0gInt2YWx1ZX0iKSkgICAlPiUKICBoY19zdWJ0aXRsZSh0ZXh0ID0gIlN1bW1hcnkgc3RhdGlzdGljcyBncm91cGVkIGJ5IGRheSIpICU+JQogIGhjX2NyZWRpdHMoCiAgICBlbmFibGVkID0gVFJVRSwgdGV4dCA9ICJEYW1pYW5vIDtwL2NsaWNrIiwKICAgIGhyZWYgPSAiaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9kYW1pYW5wYW5layIKICApCgoKaGNfYnlfZGF5CmBgYAoKYGBge3J9CmZyZXFfYnlfbW9udGhfdHJpcCA8LSB0cmFpbl9qb2luZWQgJT4lCiAgICBzZWxlY3QocGlja3VwX21vbnRoLCB0cmlwX2R1cmF0aW9uKSAlPiUKICAgIGdyb3VwX2J5KHBpY2t1cF9tb250aCkgICU+JQogICAgc3VtbWFyaXplKGNvdW50ICA9IG4oKSwgCiAgICAgICAgICAgICAgbWVhbl90cmlwID0gbWVhbih0cmlwX2R1cmF0aW9uLCBuYS5ybSA9IFRSVUUpLCAKICAgICAgICAgICAgICBtZWRpYW5fdHJpcCA9IG1lZGlhbih0cmlwX2R1cmF0aW9uLCBuYS5ybSA9IFRSVUUpLCAKICAgICAgICAgICAgICBzZF90cmlwID0gc2QodHJpcF9kdXJhdGlvbiwgbmEucm0gPSBUUlVFKSkKCmRhdGF0YWJsZShmcmVxX2J5X21vbnRoX3RyaXApCmBgYAoKYGBge3J9CmhjX2J5X21vbnRoIDwtIGhpZ2hjaGFydCgpICU+JQogIGhjX3Bsb3RPcHRpb25zKAogICAgc2VyaWVzID0gbGlzdCgKICAgICAgc2hvd0luTGVnZW5kID0gRkFMU0UsIAogICAgICBwb2ludEZvcm1hdCA9ICJ7cG9pbnQueX0lIgogICAgKSwgCiAgICBjb2x1bW4gPSBsaXN0KAogICAgICBjb2xvckJ5UG9pbnQgPSBUUlVFCiAgICApCiAgKSAlPiUgCiAgaGlnaGNoYXJ0KCkgJT4lCiAgaGNfYWRkX3NlcmllcyhmcmVxX2J5X21vbnRoX3RyaXAsICJsaW5lIiwgIGhjYWVzKHggPSBwaWNrdXBfbW9udGgsIHkgPSBtZWFuX3RyaXApLG5hbWUgPSAiTWVhbiIpICU+JQogIGhjX2FkZF9zZXJpZXMoZnJlcV9ieV9tb250aF90cmlwLCAgICJsaW5lIiAsIGhjYWVzKHg9ICBwaWNrdXBfbW9udGgsICB5PSBtZWRpYW5fdHJpcCksIG5hbWUgPSAibWVkaWFuIikgJT4lCiAgaGNfYWRkX3NlcmllcyhmcmVxX2J5X21vbnRoX3RyaXAsICJsaW5lIiwgaGNhZXMoeCA9ICBwaWNrdXBfbW9udGgsIHkgPSBzZF90cmlwKSwgbmFtZSA9ICJzZCIpICU+JSAKICBoY194QXhpcyhjYXRlZ29yaWVzID0gYygiMjAxNi0wMSIsICIyMDE2LTAyIiwgIjIwMTYtMDMiLCAiMjAxNi0wNCIsICIyMDE2LTA1IiwgIjIwMTYtMDYiKSkgJT4lCiAgaGNfYWRkX3RoZW1lKGhjX3RoZW1lX2dvb2dsZSgpKSAlPiUKICBoY190aXRsZSh0ZXh0ID0gIlN1bW1hcnkgc3RhdGlzdGljcyBieSBNb250aCBvZiBwaWNrdXAgOikiKQogIApoY19ieV9tb250aCAgICAgICAgICAgICAgICAKYGBgCgojIyMgTGVhZmxldCBzZWN0aW9uCi0g66i87KCAIOyInOyEnOulvCDrp4zrk6TroKTrqbQg7ZaJ7J2EIOuBjOyWtOyEnCDshKDtg53tlbTslbwg7ZWp64uI64ukLiDri6TsnYwgbWFrZWNsdXN0ZXIg7Ji17IWY7J2EIOyCrOyaqe2VmOyXrCDsoITri6jsnYQg7J6R7ISx7ZWY6riw66GcIOqysOygle2WiOyKteuLiOuLpC4KCmBgYHtyfQojaW5zdGFsbC5wYWNrYWdlcygnbGVhZmxldC5leHRyYXMnKQpsaWJyYXJ5KGxlYWZsZXQpCmxpYnJhcnkobGVhZmxldC5leHRyYXMpCgpsb25fbGF0IDwtIHRyYWluX2pvaW5lZFssIGMoInBpY2t1cF9sb25naXR1ZGUiLCAicGlja3VwX2xhdGl0dWRlIiwgCiJkcm9wb2ZmX2xvbmdpdHVkZSIsICJkcm9wb2ZmX2xhdGl0dWRlIildCgpsb25fbGF0JHJvd24gPC0gYXMubnVtZXJpYyhyb3duYW1lcyhsb25fbGF0KSkKCmxvbl9taW4gPC0gbG9uX2xhdFtyb3duIDwgMzAwICxdCnN0cihsb25fbWluKQpkcm9wIDwtIGxvbl9taW5bLCBjKCJwaWNrdXBfbG9uZ2l0dWRlIiwgInBpY2t1cF9sYXRpdHVkZSIsICJyb3duIildCnBpY2sgPC0gbG9uX21pblssIGMoImRyb3BvZmZfbG9uZ2l0dWRlIiwgImRyb3BvZmZfbGF0aXR1ZGUiLCAicm93biIpXQoKY29sbmFtZXMoZHJvcCkgIDwtIGMoImxvbiIsICJsYXQiLCAicm93biIpCmNvbG5hbWVzKHBpY2spIDwtIGNvbG5hbWVzKGRyb3ApCgphbGxfYmluX21pbiA8LSBiaW5kX3Jvd3MoZHJvcCwgcGljaykKYWxsX2Jpbl9taW4kcm93bjIgPC0gcmVwKDE6bnJvdyhhbGxfYmluX21pbikrMS8yLGVhY2ggPSAyKQoKCmxlYWZsZXQoZGF0YSA9IGFsbF9iaW5fbWluKSAlPiUgYWRkVGlsZXMoKSAlPiUKICBhZGRDaXJjbGVzKH5sb24sIH5sYXQpICU+JQogIGFkZFBvbHlnb25zKGRhdGEgPSBhbGxfYmluX21pbiwgbG5nID0gfmxvbiwgCiAgICAgICAgICAgICAgIGxhdCA9IH5sYXQsIAogICAgICAgICAgICAgICBzdHJva2UgPSAwLjAzLCBjb2xvciA9ICAiYmx1ZSIsIHdlaWdodCA9IDAuNCwgCiAgICAgICAgICAgICAgIG9wYWNpdHkgPSAxLjIpICAlPiUgZW5hYmxlTWVhc3VyZVBhdGgoKSAKYGBgCgotIExlYWZsZXggcGxvdCB3aXRoIG1ha2VjbHVzdGVyIG9wdGlvbnMgCgpgYGB7cn0KIGxlYWZsZXQoZGF0YSA9IHRyYWluX2pvaW5lZFsxOjUwMDAwLCBdKSAlPiUgYWRkVGlsZXMoKSAlPiUKICBhZGRNYXJrZXJzKH5waWNrdXBfbG9uZ2l0dWRlLCB+cGlja3VwX2xhdGl0dWRlLCBjbHVzdGVyT3B0aW9ucyA9IG1hcmtlckNsdXN0ZXJPcHRpb25zKCkpIApgYGAKCi0gTGVhZmxldCBoZWF0bWFwIAoKYGBge3J9CnRyYWluX2NvdW50IDwtIHRyYWluX2pvaW5lZCAlPiUgCiAgICAgICAgICAgICAgICBzZWxlY3QocGlja3VwX2xhdGl0dWRlLCBwaWNrdXBfbG9uZ2l0dWRlKSAlPiUKICAgICAgICAgICAgICAgIGdyb3VwX2J5KHBpY2t1cF9sYXRpdHVkZSwgcGlja3VwX2xvbmdpdHVkZSkgJT4lCiAgICAgICAgICAgICAgICBzdW1tYXJpemUoY291bnQgPSBuKCkpCgoKdHJhaW5fY291bnQgPC0gdHJhaW5fY291bnRbdHJhaW5fY291bnQkY291bnQgPjEsXQoKCgogbGVhZmxldChkYXRhID0gdHJhaW5fY291bnQpICU+JSBhZGRUaWxlcygpICU+JSAKIGFkZEhlYXRtYXAobG5nID0gfnBpY2t1cF9sb25naXR1ZGUsIGxhdCA9IH5waWNrdXBfbGF0aXR1ZGUsIGludGVuc2l0eSA9IH5jb3VudCwKICAgICAgICAgICAgIGJsdXIgPSAyMCwgbWF4ID0gMC4wNSwgcmFkaXVzID0gMTUpCmBgYAoKLSBQaWNrdXAgZ3JvdXBlZCBieSBtb250aAoKYGBge3J9CnRyYWluX2NvdW50IDwtIHRyYWluX2pvaW5lZCAlPiUgCiAgICAgICAgICAgICAgICBzZWxlY3QocGlja3VwX2xhdGl0dWRlLCBwaWNrdXBfbG9uZ2l0dWRlLCBwaWNrdXBfbW9udGgpICU+JQogICAgICAgICAgICAgICAgZ3JvdXBfYnkocGlja3VwX2xhdGl0dWRlLCBwaWNrdXBfbG9uZ2l0dWRlLCBwaWNrdXBfbW9udGgpICU+JQogICAgICAgICAgICAgICAgc3VtbWFyaXplKGNvdW50ID0gbigpKQoKdHJhaW5fY291bnQgPC0gdHJhaW5fY291bnRbdHJhaW5fY291bnQkY291bnQgPjEsXQoKCiBsZWFmbGV0KGRhdGEgPSB0cmFpbl9jb3VudCkgJT4lIGFkZFRpbGVzKCkgJT4lIAogYWRkSGVhdG1hcChsbmcgPSB+cGlja3VwX2xvbmdpdHVkZSwgbGF0ID0gfnBpY2t1cF9sYXRpdHVkZSwKIGxheWVySWQgPSB+cGlja3VwX21vbnRoLCBncm91cCA9IH5waWNrdXBfbW9udGgsIGludGVuc2l0eSA9IH5jb3VudCwKICAgICAgICAgICAgIGJsdXIgPSAyMCwgbWF4ID0gMC4wNSwgcmFkaXVzID0gMTUpCmBgYAoKLSBGcmVxdWVuY3kgYnkgIGRheSBvZiB3ZWVrIDopCgpgYGB7cn0KY291bnRfd2Vla2RheSA8LSB0cmFpbl9qb2luZWQgJT4lCiAgICAgICAgICAgICAgICAgIHNlbGVjdCh3ZWVrZGF5KSAlPiUKICAgICAgICAgICAgICAgICAgZ3JvdXBfYnkod2Vla2RheSkgJT4lCiAgICAgICAgICAgICAgICAgIHN1bW1hcml6ZShjb3VudCA9IG4oKSkKCmNvdW50X3dlZWtkYXkgPC0gZGF0YS50YWJsZShjb3VudF93ZWVrZGF5KQoKCmNvdW50X3dlZWtkYXkgPC0gY291bnRfd2Vla2RheVtpcy5uYSh3ZWVrZGF5KSAgPT0gIEZBTFNFLCBdCgpjb3VudF93ZWVrZGF5IDwtIGRhdGEuZnJhbWUoY291bnRfd2Vla2RheSkKCnRtIDwtIHRyZWVtYXAoY291bnRfd2Vla2RheSAsIGluZGV4ID0gYygid2Vla2RheSIpLAogICAgICAgICAgICAgIHZTaXplID0gImNvdW50IikKCmhjdHJlZW1hcCh0bSkKYGBgCgo=